package org.testis.webautomationmodule.services;

import org.testis.utils.Networking;
import org.testis.utils.OsInfo;
import com.thoughtworks.selenium.DefaultSelenium;
import com.thoughtworks.selenium.Selenium;
import java.util.ArrayList;
import org.openqa.selenium.server.SeleniumServer;

/**
 *Defines an object that runs Selenium commands.
 *<h3><a name="locators"></a>Element Locators</h3><p>
 *Element Locators tell Selenium which HTML element a command refers to.
 *The format of a locator is:</p><blockquote><em>locatorType</em><strong>=</strong>
 * <em>argument</em></blockquote><p>
 *We support the following strategies for locating elements:
</p><ul><li><strong>identifier</strong>=<em>id</em>:
 *Select the element with the specified @id attribute. If no match is
 *found, select the first element whose @name attribute is <em>id</em>.
 *(This is normally the default; see below.)</li><li><strong>id</strong>=<em>id</em>:
 *Select the element with the specified @id attribute.</li><li><strong>name</strong>
 * =<em>name</em>:
 *Select the first element with the specified @name attribute.
 *<ul class="first last simple"><li>username</li><li>name=username</li></ul>
 * <p>The name may optionally be followed by one or more <em>element-filters</em>
 * separated from the name by whitespace.  If the <em>filterType</em> is not specified,
 * <strong>value</strong> is assumed.</p><ul class="first last simple">
 * <li>name=flavour value=chocolate</li></ul></li><li><strong>dom</strong>=<em>
 * javascriptExpression</em>:
 *Find an element by evaluating the specified string.  This allows you to traverse
 * the HTML Document Object
 *Model using JavaScript.  Note that you must not return a value in this string;
 * simply make it the last expression in the block.
 *<ul class="first last simple"><li>dom=document.forms['myForm'].myDropdown</li>
 * <li>dom=document.images[56]</li><li>dom=function foo()
 * { return document.links[1]; }; foo();</li></ul></li>
 * <li><strong>xpath</strong>=<em>xpathExpression</em>:
 *Locate an element using an XPath expression.
 *<ul class="first last simple"><li>xpath=//img[@alt='The image alt text']</li>
 * <li>xpath=//table[@id='table1']//tr[4]/td[2]</li>
 * <li>xpath=//a[contains(@href,'#id1')]</li>
 * <li>xpath=//a[contains(@href,'#id1')]/@class</li>
 * <li>xpath=(//table[@class='stylee'])//th[text()='theHeaderText']/../td</li>
 * <li>xpath=//input[@name='name2' and @value='yes']</li>
 * <li>xpath=//*[text()="right"]</li></ul></li>
 * <li><strong>link</strong>=<em>textPattern</em>:
 *Select the link (anchor) element which contains text matching the
 *specified <em>pattern</em>.
 *<ul class="first last simple"><li>link=The link text</li></ul></li>
 * <li><strong>css</strong>=<em>cssSelectorSyntax</em>:
 *Select the element using css selectors. Please refer to
 * <a href="http://www.w3.org/TR/REC-CSS2/selector.html">CSS2 selectors</a>,
 * <a href="http://www.w3.org/TR/2001/CR-css3-selectors-20011113/">CSS3 selectors</a>
 * for more information. You can also check the TestCssLocators test in the selenium
 * test suite for an example of usage, which is included in the downloaded selenium
 * core package.
 *<ul class="first last simple"><li>css=a[href="#id3"]</li>
 * <li>css=span#firstChild + span</li></ul>
 * <p>Currently the css selector locator supports all css1, css2 and css3
 * selectors except namespace in css3, some pseudo classes(:nth-of-type,
 * :nth-last-of-type, :first-of-type, :last-of-type, :only-of-type, :visited,
 * :hover, :active, :focus, :indeterminate) and pseudo
 * elements(::first-line, ::first-letter, ::selection, ::before, ::after). </p></li>
 * </ul><p>
 *Without an explicit locator prefix, Selenium uses the following default
 *strategies:
 *</p><ul class="simple"><li><strong>dom</strong>, for locators starting with
 * "document."</li><li><strong>xpath</strong>, for locators starting with "//"</li>
 * <li><strong>identifier</strong>, otherwise</li></ul>
 * <h3><a name="element-filters">Element Filters</a></h3><blockquote>
 * <p>Element filters can be used with a locator to refine a list of candidate
 * elements.  They are currently used only in the 'name' element-locator.</p>
 * <p>Filters look much like locators, ie.</p><blockquote><em>filterType</em>
 * <strong>=</strong><em>argument</em></blockquote><p>Supported element-filters
 * are:</p><p><strong>value=</strong><em>valuePattern</em></p><blockquote>
 *Matches elements based on their values.  This is particularly useful for refining
 * a list of similarly-named toggle-buttons.</blockquote><p><strong>index=</strong>
 * <em>index</em></p><blockquote>
 *Selects a single element based on its position in the list (offset from zero).
 * </blockquote></blockquote><h3><a name="patterns"></a>String-match Patterns</h3>
 * <p>
 *Various Pattern syntaxes are available for matching string values:
</p><ul><li><strong>glob:</strong><em>pattern</em>:
 *Match a string against a "glob" (aka "wildmat") pattern. "Glob" is a
 *kind of limited regular-expression syntax typically used in command-line
 *shells. In a glob pattern, "*" represents any sequence of characters, and "?"
 *represents any single character. Glob patterns match against the entire
 *string.</li><li><strong>regexp:</strong><em>regexp</em>:
 *Match a string using a regular-expression. The full power of JavaScript
 *regular-expressions is available.</li><li><strong>exact:</strong><em>string</em>:
 *Match a string exactly, verbatim, without any of that fancy wildcard
 *stuff.</li></ul><p>
 *If no pattern prefix is specified, Selenium assumes that it's a "glob"
 *pattern.
 *</p>
 * @author nurhat
 */
public class SeleniumWebAutomationProvider extends WebAutomationProviderImpl {

    /**Defines an object that runs Selenium commands.*/
    private Selenium seleniumRC = null;
    /**Provides a server that can launch/terminate browsers and can receive remote Selenium commands
     */
    private SeleniumServer seleniumServer;
    /** the URL to open and process commands on */
    private String url;
    /** the command string used to launch the browser, e.g. "*firefox", "*iexplore" or "c:\\program files\\internet explorer\\iexplore.exe"*/
    private String mode;
    public static int serverCount = 0;
    public static ArrayList<SeleniumServer> servers =
            new ArrayList<SeleniumServer>();
    public static ArrayList<Selenium> clients =
            new ArrayList<Selenium>();
    private static int DEFAULT_SELENIUM_PORT = 4440;

    private synchronized static void increaseServerCount(SeleniumServer server,
            Selenium client) {
        clients.add(client);
        if (server != null) {
            servers.add(server);
        }
        serverCount++;
    }

    private synchronized static void decreaseServerCount() {
        if(serverCount>0)
          serverCount--;
    }

    public void addLocationStrategy(String strategyName,
            String functionDefinition) {
        getSeleniumRC().addLocationStrategy(strategyName,
                functionDefinition);
    }

    public void addSelection(String locator,
            String option) {
        getSeleniumRC().addSelection(locator,
                option);
    }

    public void altKeyDown() {
        getSeleniumRC().altKeyDown();
    }

    public void altKeyUp() {
        getSeleniumRC().altKeyUp();
    }

    public void answerOnNextPrompt(String answerString) {
        getSeleniumRC().answerOnNextPrompt(answerString);
    }

    public void allowNativeXpath(String allow) {
        getSeleniumRC().allowNativeXpath(allow);
    }

    public void assignId(String locator,
            String identifier) {
        getSeleniumRC().assignId(locator,
                identifier);
    }

    public void captureScreenshot(String filename) {
        getSeleniumRC().captureScreenshot(filename);
    }

    public void check(String locator) {
        getSeleniumRC().check(locator);
    }

    public void chooseCancelOnNextConfirmation() {
        getSeleniumRC().chooseCancelOnNextConfirmation();
    }

    public void chooseOkOnNextConfirmation() {
        getSeleniumRC().chooseOkOnNextConfirmation();
    }

    public void click(String locator) {
        getSeleniumRC().click(locator);
    }

    public void clickAt(String locator,
            String coordString) {
        getSeleniumRC().clickAt(locator,
                coordString);

    }

    public void close() {
        getSeleniumRC().close();
    }

    public void controlKeyDown() {
        getSeleniumRC().controlKeyDown();
    }

    public void controlKeyUp() {
        getSeleniumRC().controlKeyUp();
    }

    public void createCookie(String nameValuePair,
            String optionsString) {
        getSeleniumRC().createCookie(nameValuePair,
                optionsString);
    }

    public void deleteCookie(String name,
            String path) {
        getSeleniumRC().deleteCookie(name,
                path);
    }

    public void doubleClick(String locator) {
        getSeleniumRC().doubleClick(locator);
    }

    public void doubleClickAt(String locator,
            String coordString) {
        getSeleniumRC().doubleClickAt(locator,
                coordString);
    }

    public void dragAndDrop(String locator,
            String movementsString) {
        getSeleniumRC().dragAndDrop(locator,
                movementsString);
    }

    public void dragAndDropToObject(String locatorOfObjectToBeDragged,
            String locatorOfDragDestinationObject) {
        getSeleniumRC().dragAndDropToObject(locatorOfObjectToBeDragged,
                locatorOfDragDestinationObject);
    }

    public void dragdrop(String locator,
            String movementsString) {
        getSeleniumRC().dragdrop(locator,
                movementsString);
    }

    public void fireEvent(String locator,
            String event) {
        getSeleniumRC().fireEvent(locator,
                event);
    }

    public String getAlert() {
        return getSeleniumRC().getAlert();
    }

    public String[] getAllButtons() {
        return getSeleniumRC().getAllButtons();
    }

    public String[] getAllFields() {
        return getSeleniumRC().getAllFields();
    }

    public String[] getAllLinks() {
        return getSeleniumRC().getAllLinks();
    }

    public String[] getAllWindowIds() {
        return getSeleniumRC().getAllWindowIds();
    }

    public String[] getAllWindowNames() {
        return getSeleniumRC().getAllWindowNames();
    }

    public String[] getAllWindowTitles() {
        return getSeleniumRC().getAllWindowTitles();
    }

    public String getAttribute(String attributeLocator) {
        return getSeleniumRC().getAttribute(attributeLocator);
    }

    public String[] getAttributeFromAllWindows(String attributeName) {
        return getSeleniumRC().getAttributeFromAllWindows(attributeName);
    }

    public String getBodyText() {
        return getSeleniumRC().getBodyText();
    }

    public String getConfirmation() {
        return getSeleniumRC().getConfirmation();
    }

    public String getCookie() {
        return getSeleniumRC().getCookie();
    }

    public Number getCursorPosition(String locator) {
        return getSeleniumRC().getCursorPosition(locator);
    }

    public Number getElementHeight(String locator) {
        return getSeleniumRC().getElementHeight(locator);
    }

    public Number getElementIndex(String locator) {
        return getSeleniumRC().getElementIndex(locator);
    }

    public Number getElementPositionLeft(String locator) {
        return getSeleniumRC().getElementPositionLeft(locator);
    }

    public Number getElementPositionTop(String locator) {
        return getSeleniumRC().getElementPositionTop(locator);
    }

    public Number getElementWidth(String locator) {
        return getSeleniumRC().getElementWidth(locator);
    }

    public String getEval(String script) {
        return getSeleniumRC().getEval(script);
    }

    public String getExpression(String expression) {
        return getSeleniumRC().getExpression(expression);
    }

    public String getHtmlSource() {
        return getSeleniumRC().getHtmlSource();
    }

    public String getLocation() {
        return getSeleniumRC().getLocation();
    }

    /**
     * Answers which browser type is currently in-use
     * @return type of browser in use as string. e.g. *iexplore
     */
    public String getMode() {
        return mode;
    }

    public Number getMouseSpeed() {
        return getSeleniumRC().getMouseSpeed();
    }

    public String getPrompt() {
        return getSeleniumRC().getPrompt();
    }

    public String getSelectedId(String selectLocator) {
        return getSeleniumRC().getSelectedId(selectLocator);
    }

    public String[] getSelectedIds(String selectLocator) {
        return getSeleniumRC().getSelectedIds(selectLocator);
    }

    public String getSelectedIndex(String selectLocator) {
        return getSeleniumRC().getSelectedIndex(selectLocator);
    }

    public String[] getSelectedIndexes(String selectLocator) {
        return getSeleniumRC().getSelectedIndexes(selectLocator);
    }

    public String getSelectedLabel(String selectLocator) {
        return getSeleniumRC().getSelectedLabel(selectLocator);
    }

    public String[] getSelectedLabels(String selectLocator) {
        return getSeleniumRC().getSelectedLabels(selectLocator);
    }

    public String getSelectedValue(String selectLocator) {
        return getSeleniumRC().getSelectedValue(selectLocator);
    }

    public String[] getSelectedValues(String selectLocator) {
        return getSeleniumRC().getSelectedValues(selectLocator);
    }

    public String[] getSelectOptions(String selectLocator) {
        return getSeleniumRC().getSelectOptions(selectLocator);
    }

    public String getTable(String tableCellAddress) {
        return getSeleniumRC().getTable(tableCellAddress);
    }

    public String getText(String locator) {
        return getSeleniumRC().getText(locator);
    }

    public String getTitle() {
        return getSeleniumRC().getTitle();
    }

    /**
     * Gets the current url  that "Web Automation Provider" will browse for actions.
     * @return  the url to browse  and process commands on as string
     */
    public String getUrl() {
        return url;
    }

    public String getValue(String locator) {
        return getSeleniumRC().getValue(locator);
    }

    public boolean getWhetherThisFrameMatchFrameExpression(
            String currentFrameString,
            String target) {
        return getSeleniumRC().getWhetherThisFrameMatchFrameExpression(
                currentFrameString,
                target);
    }

    public boolean getWhetherThisWindowMatchWindowExpression(
            String currentWindowString,
            String target) {
        return getSeleniumRC().getWhetherThisWindowMatchWindowExpression(
                currentWindowString,
                target);
    }

    public Number getXpathCount(String xpath) {
        return getSeleniumRC().getXpathCount(xpath);
    }

    public void goBack() {
        getSeleniumRC().goBack();
    }

    public void highlight(String locator) {
        getSeleniumRC().highlight(locator);
    }

    public boolean isAlertPresent() {
        return getSeleniumRC().isAlertPresent();
    }

    public boolean isChecked(String locator) {
        return getSeleniumRC().isAlertPresent();
    }

    public boolean isConfirmationPresent() {
        return getSeleniumRC().isAlertPresent();
    }

    public boolean isEditable(String locator) {
        return getSeleniumRC().isEditable(locator);
    }

    public boolean isElementPresent(String locator) {
        return getSeleniumRC().isElementPresent(locator);
    }

    public boolean isOrdered(String locator1,
            String locator2) {
        return getSeleniumRC().isOrdered(locator1,
                locator2);
    }

    public boolean isPromptPresent() {
        return getSeleniumRC().isPromptPresent();
    }

    public boolean isSomethingSelected(String selectLocator) {
        return getSeleniumRC().isSomethingSelected(selectLocator);
    }

    public boolean isTextPresent(String text) {
        return getSeleniumRC().isTextPresent(text);
    }

    public boolean isVisible(String locator) {
        return getSeleniumRC().isVisible(locator);
    }

    public void keyDown(String locator,
            String keycode) {
        getSeleniumRC().keyDown(locator,
                keycode);
    }

    public void keyPress(String locator,
            String keycode) {
        getSeleniumRC().keyPress(locator,
                keycode);
    }

    public void keyUp(String locator,
            String keySequence) {
        getSeleniumRC().keyUp(locator,
                keySequence);
    }

    public void metaKeyDown() {
        getSeleniumRC().metaKeyDown();
    }

    public void metaKeyUp() {
        getSeleniumRC().metaKeyUp();
    }

    public void mouseDown(String locator) {
        getSeleniumRC().mouseDown(locator);
    }

    public void mouseDownAt(String locator,
            String coordString) {
        getSeleniumRC().mouseDownAt(locator,
                coordString);
    }

    public void mouseMove(String locator) {
        getSeleniumRC().mouseMove(locator);
    }

    public void mouseMoveAt(String locator,
            String coordString) {
        getSeleniumRC().mouseMoveAt(locator,
                coordString);
    }

    public void mouseOut(String locator) {
        getSeleniumRC().mouseOut(locator);
    }

    public void mouseOver(String locator) {
        getSeleniumRC().mouseOver(locator);
    }

    public void mouseUp(String locator) {
        getSeleniumRC().mouseUp(locator);
    }

    public void mouseUpAt(String locator,
            String coordString) {
        getSeleniumRC().mouseUpAt(locator,
                coordString);
    }

    public void open(String url) {
        getSeleniumRC().open(url);
    }

    public void openWindow(String url,
            String windowID) {
        getSeleniumRC().openWindow(url,
                windowID);
    }

    public void refresh() {
        getSeleniumRC().refresh();
    }

    public void removeAllSelections(String locator) {
        getSeleniumRC().removeAllSelections(locator);
    }

    public void removeSelection(String locator,
            String optionLocator) {
        getSeleniumRC().removeSelection(locator,
                optionLocator);
    }

    public void runScript(String script) {
        getSeleniumRC().runScript(script);
    }

    public void select(String selectLocator,
            String optionLocator) {
        getSeleniumRC().select(selectLocator,
                optionLocator);
    }

    public void selectFrame(String locator) {
        getSeleniumRC().selectFrame(locator);
    }

    public void selectWindow(String windowID) {
        getSeleniumRC().selectWindow(windowID);
    }

    public void setBrowserLogLevel(String logLevel) {
        getSeleniumRC().setBrowserLogLevel(logLevel);
    }

    public void setContext(String context) {
        getSeleniumRC().setContext(context);
    }

    public void setCursorPosition(String locator,
            String position) {
        getSeleniumRC().setCursorPosition(locator,
                position);
    }

    /**
     * Sets the command string used to launch the browser, e.g. "*firefox", "*iexplore" or "c:\\program files\\internet explorer\\iexplore.exe"
     * @param mode  the mode used for selecting browser to launch
     */
    public void setMode(String mode) {
        this.mode = mode;
    }

    public void setMouseSpeed(String pixels) {
        getSeleniumRC().setMouseSpeed(pixels);
    }

    public void setSpeed(String value) {
        getSeleniumRC().setSpeed(value);
    }

    public void setTimeout(String timeout) {
        getSeleniumRC().setTimeout(timeout);
    }

    /**
     * Sets the url  that "Web Automation Provider" will browse for actions.
     * @param url the url to browse  and process commands on
     */
    public void setUrl(String url) {
        this.url = url;
    }

    public void shiftKeyDown() {
        getSeleniumRC().shiftKeyDown();
    }

    public void shiftKeyUp() {
        getSeleniumRC().shiftKeyUp();
    }

    public void start() throws Exception {

        try {
            increaseServerCount(getServer(),
                    getSeleniumRC());
            if (!OsInfo.isWindows()) {
                getServer().start();
            }
            getSeleniumRC().start();
        } catch (RuntimeException ex) {
            throw ex;
        } catch (Exception ex) {
            throw ex;
        }
    }

    public void stop() {
        decreaseServerCount();
        stopAll();
    }

    private synchronized static void stopAll() {
        if (serverCount == 0) {
            for (int i = clients.size() - 1; i >= 0; i--) {
                try {
                    clients.get(i).stop();
                } catch (RuntimeException ex) {
                    ex.printStackTrace();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                clients.remove(i);
            }
            for (int i = servers.size() - 1; i >= 0; i--) {
                try {
                    servers.get(i).stop();
                } catch (RuntimeException ex) {
                    ex.printStackTrace();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
                servers.remove(i);
            }
        }
    }

    public void submit(String formLocator) {
        getSeleniumRC().submit(formLocator);
    }

    public void type(String locator,
            String value) {
        getSeleniumRC().type(locator,
                value);
    }

    public void typeKeys(String locator,
            String value) {
        getSeleniumRC().typeKeys(locator,
                value);
    }

    public void uncheck(String locator) {
        getSeleniumRC().uncheck(locator);
    }

    public void waitForCondition(String script,
            String timeout) {
        getSeleniumRC().waitForCondition(script,
                timeout);
    }

    public boolean waitForElementPresent(String locator,
            long timeout) {
        boolean found = false;
        for (int i = 0; i < timeout / 1000; i++) {
            if (!isElementPresent(locator)) {
                try {
                    Thread.sleep(timeout);
                } catch (InterruptedException e) {
                    throw new RuntimeException(
                            "SeleniumWebAutomation InterruptedException occured",
                            e);
                }
            } else {
                found = true;
                break;
            }
        }
        return found;
    }

    public String waitForElementsPresent(String[] locators,
            long timeout) {
        boolean found = false;
        int locator = 0;
        for (int i = 0; i < timeout / 1000 && !found; i++) {
            for (int j = 0; j < locators.length; j++) {
                if (isElementPresent(locators[j])) {
                    found = true;
                    locator = j;
                    break;
                }
            }
            try {
                Thread.sleep(timeout);
            } catch (InterruptedException e) {
                throw new RuntimeException(
                        "SeleniumWebAutomation InterruptedException occured",
                        e);
            }
        }
        if (found) {
            return locators[locator];
        } else {
            return "";
        }
    }

    public void waitForFrameToLoad(String frameAddress,
            String timeout) {
        getSeleniumRC().waitForFrameToLoad(frameAddress,
                timeout);
    }

    public void waitForPageToLoad(String timeout) {
        getSeleniumRC().waitForPageToLoad(timeout);
    }

    public void waitForPopUp(String windowID,
            String timeout) {
        getSeleniumRC().waitForPopUp(windowID,
                timeout);
    }

    public boolean waitForTextPresent(String text,
            long timeout) {
        boolean found = false;
        for (int i = 0; i < timeout / 1000; i++) {
            if (!isTextPresent(text)) {
                try {
                    Thread.sleep(timeout);
                } catch (InterruptedException e) {
                    throw new RuntimeException(
                            "SeleniumWebAutomation InterruptedException occured",
                            e);
                }
            } else {
                found = true;
                break;
            }
        }
        return found;
    }

    public void windowFocus() {
        getSeleniumRC().windowFocus();
    }

    public void windowMaximize() {
        getSeleniumRC().windowMaximize();
    }

    private SeleniumServer getServer() {
        if (seleniumServer == null && !OsInfo.isWindows()) {
            try {
                int port = Networking.getAvailablePort(DEFAULT_SELENIUM_PORT);
                org.openqa.selenium.server.RemoteControlConfiguration rcc =
                        new org.openqa.selenium.server.RemoteControlConfiguration();
                rcc.setPort(port);
                seleniumServer = new SeleniumServer(false,
                        rcc);
            } catch (Exception ex) {
                throw new RuntimeException("SeleniumWebAutomationProvider: Could not create selenium server due to "
                        + ex.getMessage());
            }
        }
        return seleniumServer;
    }

    private Selenium getSeleniumRC() {

        if (seleniumRC == null) {
            if (OsInfo.isWindows()) {
                seleniumRC = new DefaultSelenium("localhost",
                        DEFAULT_SELENIUM_PORT,
                        getMode(),
                        getUrl());
            } else {
                seleniumRC = new DefaultSelenium("localhost",
                        getServer().getPort(),
                        getMode(),
                        getUrl());
            }
        }
        return seleniumRC;
    }
}
